#include "stdafx.h"
#include "..\..\cz_def.h"
#include "..\..\function\cz_log.h"
#include "file_def.h"
#include "FileReaderPool.h"

#define MIN_USE_MEMORY						1  * 1024 * 1024
#define MAX_USE_MEMORY						64 * 1024 * 1024

#define INNER_FLAG_NULL						0x00000000
#define INNER_FLAG_NEED_WAIT				0x00000001

FileReaderPool::FileReaderPool()
{
	Clear();
}

FileReaderPool::~FileReaderPool()
{
	Uninit();
}

bool FileReaderPool::Init(IN stringc& strFile, IN u32 useMemory, IN u32 reservedSpace, IN u32 readFlag)
{
	DWORD flag;

	Uninit();

	m_useMemory = useMemory;
	m_reservedSpace = reservedSpace;
	m_readFlag = readFlag;

	CheckParameter();

	if ((readFlag & READ_FLAG_OVERLAPPED) != 0) flag = FILE_FLAG_OVERLAPPED;
	else flag = FILE_ATTRIBUTE_NORMAL;

	m_hFile = CreateFileA(strFile.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, flag, 0);
	if (m_hFile == INVALID_HANDLE_VALUE)
	{
		CZ_LOG_ERRORA("");
		goto L_Failed;
	}

	if (GetFileSizeEx(m_hFile, (LARGE_INTEGER*)&m_fileSize) == FALSE) 
	{
		CZ_LOG_ERRORA("");
		goto L_Failed;
	}

	if ((readFlag & READ_FLAG_OVERLAPPED) != 0)
	{
		m_poolSize = m_useMemory / 2;
		m_pool1 = (u8*)malloc(m_poolSize);
		if (m_pool1 == NULL)
		{
			CZ_LOG_ERRORA("");
			goto L_Failed;
		}
		m_pool2 = (u8*)malloc(m_poolSize);
		if (m_pool2 == NULL)
		{
			CZ_LOG_ERRORA("");
			goto L_Failed;
		}
	}
	else
	{
		m_poolSize = m_useMemory;
		m_pool1 = (u8*)malloc(m_poolSize);
		if (m_pool1 == NULL)
		{
			CZ_LOG_ERRORA("");
			goto L_Failed;
		}
	}

	return true;

L_Failed:
	Uninit();
	return false;
}

void FileReaderPool::Uninit()
{
	if ((m_innerFlag & INNER_FLAG_NEED_WAIT) != 0)
	{
		DWORD dwBytesRead;
		GetOverlappedResult(m_hFile, &m_tempOverLapped2, &dwBytesRead, TRUE);
	}
	if (m_hFile != INVALID_HANDLE_VALUE) CloseHandle(m_hFile);
	if (m_pool1 != NULL) free(m_pool1);
	if (m_pool2 != NULL) free(m_pool2);

	Clear();
}

u64 FileReaderPool::GetFileSize()
{
	return m_fileSize;
}

bool FileReaderPool::GetData(IN u64 offset, OUT u8* data, IN u32 size)
{
	if (!CheckSize(0, m_fileSize, offset, size))
	{
		CZ_LOG_ERRORA("");
		return false;
	}

	if (!CheckSize(m_fileOffset1, m_fillSize1, offset, size))
	{
		if (!RePool(offset, size))
		{
			if (!ReadFileImmediately(offset, data, size))
			{
				CZ_LOG_ERRORA("");
				return false;
			}
		}
	}
		
	memcpy(data, m_pool1 + offset - m_fileOffset1, size);

	return true;
}

bool FileReaderPool::Getu64(IN u64 offset, OUT u64& v)
{
	return GetData(offset, (u8*)&v, 8);
}

bool FileReaderPool::Getu32(IN u64 offset, OUT u32& v)
{
	return GetData(offset, (u8*)&v, 4);
}

bool FileReaderPool::Getu16(IN u64 offset, OUT u16& v)
{
	return GetData(offset, (u8*)&v, 2);
}

bool FileReaderPool::Getu8(IN u64 offset, OUT u8& v)
{
	return GetData(offset, &v, 1);
}

void FileReaderPool::SetReservedSpace(IN u32 reservedSpace)
{
	m_reservedSpace = reservedSpace;
}

void FileReaderPool::Clear()
{
	m_useMemory = 0;
	m_reservedSpace = 0;
	m_readFlag = READ_FLAG_NULL;

	m_hFile = INVALID_HANDLE_VALUE;
	m_fileSize = 0;
	m_poolSize = 0;
	m_innerFlag = INNER_FLAG_NULL;

	m_pool1 = NULL;
	m_fileOffset1 = 0;
	m_fillSize1 = 0;

	m_pool2 = NULL;
	m_fileOffset2 = 0;
	m_fillSize2 = 0;

	memset(&m_tempOverLapped2, 0, sizeof(OVERLAPPED));
	m_tempFileOffset2 = 0;
	m_tempFillSize2 = 0;
}

void FileReaderPool::CheckParameter()
{
	if (m_useMemory < MIN_USE_MEMORY) m_useMemory = MIN_USE_MEMORY;
	else if (m_useMemory > MAX_USE_MEMORY) m_useMemory = MAX_USE_MEMORY;

	if (((m_readFlag & READ_FLAG_FORWARD) != 0) &&
		((m_readFlag & READ_FLAG_BACKWARD) != 0)) m_readFlag &= ~READ_FLAG_BACKWARD;
	else if (((m_readFlag & READ_FLAG_FORWARD) == 0) &&
		((m_readFlag & READ_FLAG_BACKWARD) == 0)) m_readFlag |= READ_FLAG_FORWARD;
}

bool FileReaderPool::RePool(IN u64 offset, IN u32 size)
{
	if ((m_readFlag & READ_FLAG_OVERLAPPED) != 0)
	{
		if (!RePoolOverlapped(offset, size))
		{
			CZ_LOG_ERRORA("");
			return false;
		}
	}
	else
	{
		if (!RePoolNoOverlapped(offset, size))
		{
			CZ_LOG_ERRORA("");
			return false;
		}
	}

	return true;
}

bool FileReaderPool::RePoolNoOverlapped(IN u64 offset, IN u32 size)
{
	if (!ReadFileToPool1(offset, size))
	{
		CZ_LOG_ERRORA("");
		return false;
	}

	return true;
}

bool FileReaderPool::RePoolOverlapped(IN u64 offset, IN u32 size)
{
	if ((m_innerFlag & INNER_FLAG_NEED_WAIT) != 0)
	{
		DWORD dwBytesRead;
		m_innerFlag &= ~INNER_FLAG_NEED_WAIT;
		if (GetOverlappedResult(m_hFile, &m_tempOverLapped2, &dwBytesRead, TRUE) == FALSE)
		{
			CZ_LOG_ERRORA("");
			return false;
		}
		m_fileOffset2 = m_tempFileOffset2;
		m_fillSize2 = m_tempFillSize2;
	}

	if (CheckSize(m_fileOffset2, m_fillSize2, offset, size))
	{
		// hit pool2
		ChangePool();
	}
	else
	{
		// not hit pool2
		if (!ReadFileToPool1(offset, size))
		{
			CZ_LOG_ERRORA("");
			return false;
		}
	}

	if (!ReadFileToPool2())
	{
		CZ_LOG_ERRORA("");
		return false;
	}

	return true;
}

void FileReaderPool::ChangePool()
{
	u8* t;
	t = m_pool1, m_pool1 = m_pool2, m_pool2 = t;
	m_fileOffset1 ^= m_fileOffset2 ^= m_fileOffset1 ^= m_fileOffset2;
	m_fillSize1 ^= m_fillSize2 ^= m_fillSize1 ^= m_fillSize2;
}

bool FileReaderPool::ReadFileToPool1(IN u64 offset, IN u32 size)
{
	u64 tempFileOffset;
	u32 tempFillSize;

	if (!CalcFileOffsetAndFillSize(offset, size, tempFileOffset, tempFillSize))
	{
		CZ_LOG_ERRORA("");
		return false;
	}
	if (!ReadFileImmediately(tempFileOffset, m_pool1, tempFillSize))
	{
		CZ_LOG_ERRORA("");
		return false;
	}

	m_fileOffset1 = tempFileOffset;
	m_fillSize1 = tempFillSize;

	return true;
}

bool FileReaderPool::ReadFileToPool2()
{
	DWORD dwBytesRead;
	u64 offset;

	// calculate fileOffset & fillSize
	if ((m_readFlag & READ_FLAG_FORWARD) != 0) offset = m_fileOffset1 + m_fillSize1;
	else if ((m_readFlag & READ_FLAG_BACKWARD) != 0) offset = m_fileOffset1;
	if (!CalcFileOffsetAndFillSize(offset, 0, m_tempFileOffset2, m_tempFillSize2))
	{
		CZ_LOG_ERRORA("");
		return false;
	}

	// read file
	memset(&m_tempOverLapped2, 0, sizeof(OVERLAPPED));
	m_tempOverLapped2.Offset = ((LARGE_INTEGER*)&m_tempFileOffset2)->LowPart;
	m_tempOverLapped2.OffsetHigh = ((LARGE_INTEGER*)&m_tempFileOffset2)->HighPart;

	m_innerFlag |= INNER_FLAG_NEED_WAIT;

	if (ReadFile(m_hFile, m_pool2, m_tempFillSize2, &dwBytesRead, &m_tempOverLapped2) == FALSE)
	{
		if (GetLastError() != ERROR_IO_PENDING)
		{
			m_innerFlag &= ~INNER_FLAG_NEED_WAIT;
			CZ_LOG_ERRORA("");
			return false;
		}
	}

	return true;
}

bool FileReaderPool::ReadFileImmediately(IN u64 offset, OUT u8* data, INOUT u32& size)
{
	OVERLAPPED overLapped;
	DWORD dwBytesRead;
	u32 t;

	if ((m_readFlag & READ_FLAG_OVERLAPPED) != 0)
	{
		memset(&overLapped, 0, sizeof(OVERLAPPED));
		overLapped.Offset = ((LARGE_INTEGER*)&offset)->LowPart;
		overLapped.OffsetHigh = ((LARGE_INTEGER*)&offset)->HighPart;

		if (ReadFile(m_hFile, data, size, &dwBytesRead, &overLapped) == FALSE)
		{
			if (GetLastError() != ERROR_IO_PENDING)
			{
				CZ_LOG_ERRORA("");
				return false;
			}
		}
		if (GetOverlappedResult(m_hFile, &overLapped, &dwBytesRead, TRUE) == FALSE)
		{
			CZ_LOG_ERRORA("");
			return false;
		}
		size = dwBytesRead;
	}
	else
	{
		if (SetFilePointerEx(m_hFile, *(LARGE_INTEGER*)&offset, NULL, FILE_BEGIN) == FALSE)
		{
			CZ_LOG_ERRORA("");
			return false;
		}

		t = 0;
		while(true)
		{
			if (ReadFile(m_hFile, data + t, size - t, &dwBytesRead, NULL) == FALSE)
			{
				CZ_LOG_ERRORA("");
				return false;
			}
			if (dwBytesRead == 0) break;
			t += dwBytesRead;
			if (t == size) break;
			if (t > size)
			{
				CZ_LOG_ERRORA("");
				return false;
			}
		}
		size = t;
	}

	return true;
}

bool FileReaderPool::CheckSize(IN u64 offset1, IN u64 size1, IN u64 offset2, IN u64 size2)
{
	return (!u64AddOverflow(offset2, size2) && (offset2 >= offset1) && ((offset2 + size2) <= (offset1 + size1)));
}

bool FileReaderPool::CalcFileOffsetAndFillSize(IN u64 offset, IN u32 size, OUT u64& fileOffset, OUT u32& fillSize)
{
	u64 t1, t2;

	if (size > m_poolSize)
	{
		CZ_LOG_ERRORA("");
		return false;
	}

	t1 = offset + size - min(offset + size, m_poolSize);
	if ((m_readFlag & READ_FLAG_FORWARD) != 0)
	{
		t2 = min(offset, m_reservedSpace);
		t2 = offset - min(m_poolSize, t2);
	}
	else if ((m_readFlag & READ_FLAG_BACKWARD) != 0)
	{
		t2 = min(m_fileSize - offset, m_reservedSpace);
		t2 = m_poolSize - min(m_poolSize, t2);
		t2 = offset - min(offset, t2);
	}
	fileOffset = max(t1, t2);
	fillSize = (u32)min(m_fileSize - fileOffset, m_poolSize);

	return true;
}